import scrapy
import re
from fontTools.ttLib import TTFont
from io import BytesIO
import requests


class FanqieSpider(scrapy.Spider):
    name = "fanqie"

    def start_requests(self):
        yield scrapy.Request(
            'https://fanqienovel.com/api/rank/category/list?app_id=2503&rank_list_type=3&offset=10&limit=20&category_id=1140&rank_version=&gender=1&rankMold=2&msToken=R7oho8K2r03M-Wpe3Sbndm0QcfsDiwk0wqpyH8mAt8MN3XzjrpErKAaikvbPf7IPx7TpFYHRIoNnOiBwZvxd8D6cPO1kIEZwuSNaqDP2s2myvXhG0Gw5Rg%3D%3D&a_bogus=D70mXOZwMsm1xEf0kXkz9Ctm9qg0YWRtgZENzrJLVULH',
            callback=self.parse_item
        )

    def parse_item(self, response):
        data = response.json()

        if data.get('code') != 0:
            self.logger.error(f"API请求失败: {data.get('message')}")
            return

        book_list = data.get('data', {}).get('book_list', [])

        for item in book_list:
            # 书籍基本信息
            cleaned_item = {
                'book_id': item.get('bookId'),
                'title': clean_text(item.get('bookName')),
                'author': clean_text(item.get('author')),
                'category': item.get('category', ''),
                'status': '连载中' if item.get('creationStatus') == '1' else '已完结',
                'word_count': item.get('wordNumber'),
                'abstract': clean_text(item.get('abstract', '')),
                'cover_url': item.get('thumbUri', ''),
                'rank': item.get('currentPos'),
                'read_count': item.get('read_count'),
                'update_time': item.get('lastChapterUpdateTime', ''),
                'last_chapter': clean_text(item.get('lastChapterTitle', '')),
                'rank_change': item.get('rankPosDiff', 0)
            }

            # 请求书籍页面获取章节列表
            yield scrapy.Request(
                f"https://fanqienovel.com/page/{cleaned_item['book_id']}?enter_from=Rank",
                callback=self.parse_chapter,
                meta={'book_info': cleaned_item}
            )

            # 返回书籍信息
            yield cleaned_item

    def parse_chapter(self, response):
        """解析章节列表"""
        book_info = response.meta['book_info']
        chapters = response.xpath('//div[@class="chapter"]/div[@class="chapter-item"]')

        self.logger.info(f"书籍《{book_info['title']}》找到 {len(chapters)} 个章节")

        # 只爬取前5章进行测试
        for idx, chapter in enumerate(chapters[:5]):
            chapter_url = chapter.xpath('.//a/@href').get()
            chapter_title = chapter.xpath('.//a/text()').get()

            if chapter_url:
                yield scrapy.Request(
                    f'https://fanqienovel.com{chapter_url}',
                    callback=self.parse_detail,
                    meta={
                        'book_info': book_info,
                        'chapter_title': chapter_title,
                        'chapter_order': idx + 1
                    }
                )

    def parse_detail(self, response):
        """解析章节内容"""
        book_info = response.meta['book_info']

        # 获取字体映射
        font_map = self.parse_font_from_response(response)

        # 提取原始内容
        content_elements = response.xpath('//div[@class="muye-reader-content noselect"]//p//text()').getall()
        raw_content = ''.join(content_elements)

        # 解密内容
        decoded_content = ''
        for char in raw_content:
            decoded_content += font_map.get(char, char)

        # 章节信息
        item = {
            # 书籍信息
            'book_id': book_info['book_id'],
            'book_title': book_info['title'],
            'book_author': book_info['author'],
            'book_category': book_info['category'],

            # 章节信息
            'chapter_title': response.xpath('//h1[@class="muye-reader-title"]/text()').get() or response.meta.get(
                'chapter_title', ''),
            'chapter_url': response.url,
            'chapter_order': response.meta.get('chapter_order', 0),

            # 最重要的文章内容
            'content': clean_text(decoded_content),
            'content_length': len(decoded_content),

            # 调试信息
            'raw_content_sample': raw_content[:200]  # 原始内容样本，用于调试
        }

        self.logger.info(f"章节保存: {item['chapter_title']}, 内容长度: {item['content_length']}")

        # 如果内容太短，可能是字体映射问题
        if item['content_length'] < 100:
            self.logger.warning(f"⚠️ 内容可能不完整，只有 {item['content_length']} 字符")

        yield item

    def parse_font_from_response(self, response):
        """解析字体文件"""
        font_url = response.xpath('//style[contains(text(), "font-face")]/text()').re_first(
            r'url\(["\']?([^"\'\)]+)["\']?\)')

        if font_url:
            if font_url.startswith('//'):
                font_url = 'https:' + font_url

            try:
                font_response = requests.get(font_url, timeout=10)
                font = TTFont(BytesIO(font_response.content))

                # 创建完整的字体映射
                font_map = {}
                for table in font['cmap'].tables:
                    for code, name in table.cmap.items():
                        actual_char = self.map_font_to_char(name)
                        if actual_char:
                            font_map[chr(code)] = actual_char

                self.logger.info(f"✅ 字体解析成功: {len(font_map)} 个字符映射")
                return font_map

            except Exception as e:
                self.logger.error(f"❌ 字体解析失败: {e}")

        # 如果字体解析失败，使用备用映射
        return self.get_fallback_font_map()

    def map_font_to_char(self, font_name):
        """将字体名称映射到实际字符"""
        # 更完整的映射表
        mapping = {
            'uniE000': ' ', 'uniE001': '，', 'uniE002': '。', 'uniE003': '！', 'uniE004': '？',
            'uniE005': '；', 'uniE006': '：', 'uniE007': '"', 'uniE008': '（', 'uniE009': '）',
            'uniE00A': '《', 'uniE00B': '》', 'uniE00C': '【', 'uniE00D': '】', 'uniE00E': '·',
            'uniE00F': '—', 'uniE010': '~', 'uniE011': '‘', 'uniE012': '’', 'uniE013': '“',
            'uniE014': '”', 'uniE015': '〔', 'uniE016': '〕', 'uniE017': '〈', 'uniE018': '〉',
            'uniE019': '、', 'uniE01A': '〃', 'uniE01B': '〆', 'uniE01C': '〇', 'uniE01D': '〈',
            'uniE01E': '〉', 'uniE01F': '《', 'uniE020': '》', 'uniE021': '「', 'uniE022': '」',
            'uniE023': '『', 'uniE024': '』', 'uniE025': '〔', 'uniE026': '〕', 'uniE027': '…',
            'uniE028': '—', 'uniE029': '～', 'uniE02A': '‖', 'uniE02B': '∶', 'uniE02C': '＂',
            'uniE02D': '＇', 'uniE02E': '｀', 'uniE02F': '｜', 'uniE030': '〃', 'uniE031': '　',
            'uniE032': '、', 'uniE033': '。', 'uniE034': '〃', 'uniE035': '〄', 'uniE036': '々',
            'uniE037': '〆', 'uniE038': '〇', 'uniE039': '〈', 'uniE03A': '〉', 'uniE03B': '《',
            'uniE03C': '》', 'uniE03D': '「', 'uniE03E': '」', 'uniE03F': '『', 'uniE040': '』',
            'uniE041': '【', 'uniE042': '】', 'uniE043': '〒', 'uniE044': '〓', 'uniE045': '〔',
            'uniE046': '〕', 'uniE047': '〖', 'uniE048': '〗', 'uniE049': '〘', 'uniE04A': '〙',
            'uniE04B': '〚', 'uniE04C': '〛', 'uniE04D': '〜', 'uniE04E': '〝', 'uniE04F': '〞',
            'uniE050': '〟', 'uniE051': '〠', 'uniE052': '〡', 'uniE053': '〢', 'uniE054': '〣',
            'uniE055': '〤', 'uniE056': '〥', 'uniE057': '〦', 'uniE058': '〧', 'uniE059': '〨',
            'uniE05A': '〩', 'uniE05B': '〪', 'uniE05C': '〫', 'uniE05D': '〬', 'uniE05E': '〭',
            'uniE05F': '〮', 'uniE060': '〯', 'uniE061': '〰', 'uniE062': '〱', 'uniE063': '〲',
            'uniE064': '〳', 'uniE065': '〴', 'uniE066': '〵', 'uniE067': '〶', 'uniE068': '〷',
            'uniE069': '〸', 'uniE06A': '〹', 'uniE06B': '〺', 'uniE06C': '〻', 'uniE06D': '〼',
            'uniE06E': '〽', 'uniE06F': '〾', 'uniE070': '〿',
        }
        return mapping.get(font_name)

    def get_fallback_font_map(self):
        """备用字体映射表"""
        # 如果动态解析失败，使用这个基础映射
        return {
            '': '龚', '': '二', '': '狗', '': '，', '': '今',
            '': '年', '': '十', '': '六', '': '岁', '': '在',
            '': '这', '': '个', '': '世', '': '界', '': '已',
            '': '。', '': '尽', '': '管', '': '他', '': '一',
            '': '直', '': '都', '': '很', '': '劲', '': '的',
            '': '但', '': '是', '': '门', '': '上', '': '还',
            '': '是', '': '给', '': '—', '': '—', '': '龚',
            '': '是', '': '样', '': '可', '': '变', '': '的',
            '': '命', '': '在', '': '头', '': '的', '': '乾',
            '': '九', '': '魏', '': '怎', '': '您', '': '雁',
            '': '腰', '': '临', '': '江', '': '怎', '': '王',
            '': '贵', '': '族', '': '洗', '': '澡', '': '尽',
            '': '族', '': '贵', '': '王', '': '怎', '': '江',
            '': '临', '': '乾', '': '坤', '': '私', '': '伪',
            '': '邪', '': '布', '': '凶', '': '陈', '': '穿',
            '': '越', '': '村', '': '余', '': '婚', '': '宴',
            '': '噬', '': '求', '': '仙', '': '毁', '': '形',
            '': '念', '': '蓬', '': '咒', '': '号', '': '令',
            '': '但', '': '祖', '': '垂', '': '怜', '': '赐',
            '': '九', '': '都', '': '泉', '': '号', '': '令',
            '': '启', '': '酆', '': '阎', '': '浮', '': '誓',
            '': '杀', '': '清', '': '净'
        }


def clean_text(text):
    """清理文本，保留基本内容"""
    if not text:
        return ""
    # 保留中文、英文、数字、常见标点
    pattern = re.compile(r'[^\u4e00-\u9fa5a-zA-Z0-9\s，。！？；："（）《》【】·—~]')
    cleaned = pattern.sub('', text)
    return cleaned.strip()